package org.gbif.map{
	
	import com.google.maps.Color;
	import com.google.maps.Copyright;
	import com.google.maps.CopyrightCollection;
	import com.google.maps.LatLng;
	import com.google.maps.LatLngBounds;
	import com.google.maps.Map;
	import com.google.maps.MapMouseEvent;
	import com.google.maps.TileLayerBase;
	import com.google.maps.interfaces.IPane;
	import com.google.maps.overlays.EncodedPolylineData;
	import com.google.maps.overlays.Polygon;
	import com.google.maps.overlays.PolygonOptions;
	import com.google.maps.styles.FillStyle;
	import com.google.maps.styles.StrokeStyle;
	
	import flash.display.DisplayObject;
	import flash.display.Sprite;
	import flash.events.TimerEvent;
	import flash.geom.Point;
	import flash.utils.Dictionary;
	import flash.utils.Timer;
	import flash.utils.getTimer;
	
	import gs.TweenLite;
	
	import mx.collections.ArrayCollection;
	import mx.core.Application;
	import mx.formatters.NumberFormatter;
	import mx.rpc.events.FaultEvent;
	import mx.rpc.events.ResultEvent;
	import mx.rpc.remoting.mxml.RemoteObject;
	
	public class GbifPolygonOccurrenceGridTileLayer extends TileLayerBase {
		
		private var tileSize:Number=256;
		private var map:Map;
		private var srvMapGrid0:RemoteObject;
		private var srvMapGrid1:RemoteObject;
		private var srvMapGrid2:RemoteObject;
		private var srvMapGrid3:RemoteObject;
		private	var paletteOne:Array =   			[0xFBFF9D,0xFFE86A,0xFFBF49,0xFF6727,0xFF2614,0xDB0000,0xD36800,0xE20F00,0xC78F00,0xD04B00,0xD60000];
		private	var paletteYellowRed10:Array =   	[0xFABA00,0xFAB000,0xFD8D00,0xF97A00,0xFC5D0E,0xFD3F16,0xFC3117,0xFE211D,0xFE091F,0xFC001F];
		private	var paletteTwo:Array =   			[0xB9B5C0,0x818181,0x67674F,0x8C8B44,0xA7A626,0xBDBB00,0xC99200,0xD36800,0xD3411E,0xE20F00];  
		private	var paletteThree:Array = 			[0x232859,0x1E4F65,0x1C666A,0x1A794F,0x219015,0x69A90B,0xC2BA00,0xC2BA00,0xC78F00,0xD04B00,0xD60000];  
		
		private var loadedTiles:Dictionary = new Dictionary();
		private var cacheRequest:Dictionary = new Dictionary();
		private var polygonCache:Dictionary = new Dictionary();
		private var gridPane:IPane;		
		
		public var previousClickedPolygon:Polygon;
		public var previousClickedPolygonOptions:PolygonOptions;
		
		public var currentZoomLevel:Number;
		public var previousZoomLevel:Number;
		private var previousZoomLevelCleared:Boolean=false;
		private var numFormatter:NumberFormatter= new NumberFormatter();
		private var polygonId:Number;
		private var servicesRunning:Number=0;
		
		private var onZoomTransition:Boolean=false;
		
		private var srvNum:Number=0;
		
		private var timers:Dictionary=new Dictionary();
		
		private var doubleClickEvent:Boolean = false; 
		private var clickCell:Point;
		private var clickTimer:Timer;
		
		public function GbifPolygonOccurrenceGridTileLayer(polygonId:Number, map:Map) {
			
			
			srvMapGrid0= new RemoteObject("wdpaManager");
			srvMapGrid0.source="wdpaManager";
			srvMapGrid0.showBusyCursor=false;
			srvMapGrid0.endpoint="http://wdpa-dev0.gbif.org/graniteamf";
			srvMapGrid0.addEventListener(ResultEvent.RESULT,srvMapGridResultHandler);
			srvMapGrid0.addEventListener(FaultEvent.FAULT,srvMapGridFaultHandler);
			srvMapGrid1= new RemoteObject("wdpaManager");
			srvMapGrid1.source="wdpaManager";
			srvMapGrid1.showBusyCursor=false;
			srvMapGrid1.endpoint="http://wdpa-dev0.gbif.org/graniteamf";
			srvMapGrid1.addEventListener(ResultEvent.RESULT,srvMapGridResultHandler);
			srvMapGrid1.addEventListener(FaultEvent.FAULT,srvMapGridFaultHandler);
			srvMapGrid2= new RemoteObject("wdpaManager");
			srvMapGrid2.source="wdpaManager";
			srvMapGrid2.showBusyCursor=false;
			srvMapGrid2.endpoint="http://wdpa-dev0.gbif.org/graniteamf";
			srvMapGrid2.addEventListener(ResultEvent.RESULT,srvMapGridResultHandler);
			srvMapGrid2.addEventListener(FaultEvent.FAULT,srvMapGridFaultHandler);
			srvMapGrid3= new RemoteObject("wdpaManager");
			srvMapGrid3.source="wdpaManager";
			srvMapGrid3.showBusyCursor=false;
			srvMapGrid3.endpoint="http://wdpa-dev0.gbif.org/graniteamf";
			srvMapGrid3.addEventListener(ResultEvent.RESULT,srvMapGridResultHandler);
			srvMapGrid3.addEventListener(FaultEvent.FAULT,srvMapGridFaultHandler);
			  
			var copyrightCollection:CopyrightCollection = new CopyrightCollection();
			copyrightCollection.addCopyright(new Copyright("ennefox", new LatLngBounds(new LatLng(-180, 90), new LatLng(180, -90)), 21,"ennefox"));
			super(copyrightCollection, 0, 21);
			
			for (var i:Number=0;i<=17;i++) {
            	loadedTiles[i]=new Dictionary();
            }
			
			this.map = map;		
			this.polygonId = polygonId;
				
			currentZoomLevel=map.getZoom();
			gridPane= map.getPaneManager().createPane(100);
			
			numFormatter.useThousandsSeparator=true;
			
		}

		public override function loadTile(tilePos:Point, zoom:Number):DisplayObject {


			var isNecessaryToLoad:Boolean=true;
			var parentPoint:Point = new Point(Math.floor(tilePos.x/2),Math.floor(tilePos.y/2));		    
		    var previousZoomLoaded:Boolean =false;
		    for each(var o:Object in loadedTiles[zoom-1]) {
		    	previousZoomLoaded=true;
		    	break;
		    }

		    try {
			    if (previousZoomLoaded) {
				    if((loadedTiles[zoom-1][parentPoint.x+"/"+parentPoint.y] as Array).length>0) {
				    	isNecessaryToLoad=true;
				    } else {
				    	isNecessaryToLoad=false;		    	
				    }
			    } else {
			    	isNecessaryToLoad=true;
			    }
		    } catch(e:Error) {
		    	
		    }
		    
		    
		    if (currentZoomLevel!=zoom) {
				previousZoomLevel=currentZoomLevel;		
				previousZoomLevelCleared=false;    	
		    }
		    currentZoomLevel=zoom;
		    
		    if (isNecessaryToLoad) {		    
			    if (!cacheRequest[tilePos.x+"/"+tilePos.y+"/"+zoom]) {
		           	srvNum++;
		           	if (srvNum>3)
		           		srvNum=0;
				    
					timers[tilePos.x+"/"+tilePos.y+"/"+zoom]=getTimer();
					
				   	if (srvNum==0) {
				    	srvMapGrid0.getAreaCellsByTileArea(tilePos.x,tilePos.y,zoom,polygonId);
				    } else if(srvNum==1) {
				    	srvMapGrid1.getAreaCellsByTileArea(tilePos.x,tilePos.y,zoom,polygonId);
				    } else if(srvNum==2) {
				    	srvMapGrid1.getAreaCellsByTileArea(tilePos.x,tilePos.y,zoom,polygonId);		    
				    } else if(srvNum==3) {
				    	srvMapGrid1.getAreaCellsByTileArea(tilePos.x,tilePos.y,zoom,polygonId);
				    }
				    
			    	cacheRequest[tilePos.x+"/"+tilePos.y+"/"+zoom]=true;
				    servicesRunning++;
				    Application.application.mapCanvas.dataLoading=true;
			    } else {
			    	//it is already on the cache
					for each (var pol:Polygon in loadedTiles[zoom][tilePos.x+"/"+tilePos.y]) {
						addPolygonSmooth(pol);					
					}		
					
					clearPolygonsFromZoomLevel(previousZoomLevel); 	
			    }
		    }
		    
			
			return new Sprite();
		}
		
		private function srvMapGridFaultHandler(ev:FaultEvent):void {
			trace(ev.message);
		}
		

	    private function srvMapGridResultHandler(ev:ResultEvent):void {
			
			//trace((getTimer() - timers[ev.result.params.tileX+"/"+ev.result.params.tileY +"/"+ ev.result.params.zoom]) +" ms.  - "+ ev.result.grids.length +"res.");
	
			
	    	servicesRunning--;
	    	if (servicesRunning>0) {
		    	Application.application.mapCanvas.dataLoading=true;
	    	} else {
		    	Application.application.mapCanvas.dataLoading=false;	    		
	    	}
 			var gridData:ArrayCollection = ev.result.grids as ArrayCollection;
 			var ta:Array = loadedTiles[ev.result.params.zoom][ev.result.params.tileX+"/"+ev.result.params.tileY]= new Array();
 			var z:Number=ev.result.params.zoom;
 			if(z<3)
 				z=2;
		 	for each(var cell:Object in gridData) {
		 		//num_occ,num_specimens,num_observations,num_other_bor,num_plantae,num_animalia,num_other_kingdoms
		 		var stats:Array=[cell.num_occ,cell.num_specimens,cell.num_observations,cell.num_other_bor,cell.num_plantae,cell.num_animalia,cell.num_other_kingdoms];
		 		var poli:Polygon;
		 		if (cell.border_geom != null && cell.border_geom !='') {
		 			var encodedPoints:String = cell.border_geom.encodedPoints;
    				var encodedLevels:String = cell.border_geom.encodedLevels;
			 		poli = Polygon.fromEncoded([new EncodedPolylineData(encodedPoints, 2, encodedLevels, 18)],getPolygonOptions(stats,ev.result.params.tileLimits.source as Array));
		 		} else {
			 		poli = new Polygon(getPolygonCoordsFromTileCoords(cell.tilex,cell.tiley,z+2),getPolygonOptions(stats,ev.result.params.tileLimits.source as Array));
		 		}
		 		
		 		poli.foreground.alpha=0;
		 		ta.push(poli);
		 		polygonCache[poli]=new Point(cell.tilex,cell.tiley);
		 		if (ev.result.params.zoom == map.getZoom()) {
					addPolygonSmooth(poli);
		 		}
			}	 			
		    //clear previous polygons
			clearPolygonsFromZoomLevel(previousZoomLevel);
		
	    }
	    
	    private function onRollOver(ev:MapMouseEvent):void {			
 			previousClickedPolygon=ev.target as Polygon;
 			previousClickedPolygonOptions = previousClickedPolygon.getOptions();
 			
  			(ev.target as Polygon).setOptions(
 				new PolygonOptions({fillStyle: 
 					new FillStyle({color:Color.BLACK}),
 					strokeStyle:
 					new StrokeStyle({color:Color.WHITE, thickness:6})
 					}));
		}
	    
	    private function onRollOut(ev:MapMouseEvent):void {
  			if(previousClickedPolygon !=null) {
 				previousClickedPolygon.setOptions(previousClickedPolygonOptions);
 			}	    	
	    }
	    
	    private function onCellClick(ev:MapMouseEvent):void {
	    	doubleClickEvent = false; 
	    	clickCell = polygonCache[ev.target] as Point;
	        clickTimer = new Timer (300, 1); 
	        clickTimer.addEventListener(TimerEvent.TIMER, doClickTimer); 
	        clickTimer.start (); 	  
	          	
	    }
	    
		private function onDoubleClick (e:MapMouseEvent):void { 
		        doubleClickEvent = true; 
		        clickCell = null; 
		} 	    
		private function doClickTimer (e:TimerEvent):void { 
	        if (doubleClickEvent || clickCell == null) { 
	                return; 
	        }  else {
		    	Application.application.mapCanvas.onCellClicked(clickCell,currentZoomLevel);
	        }
		} 	    
	      
	    private function addPolygonSmooth(pol:Polygon):void {
	    	pol.foreground.alpha=0;
	    	gridPane.addOverlay(pol);
	    	TweenLite.to(pol.foreground,1.0,{alpha:0.6});
			pol.addEventListener(MapMouseEvent.ROLL_OVER,onRollOver,false,0,true);
			pol.addEventListener(MapMouseEvent.ROLL_OUT,onRollOut,false,0,true);
	 		pol.addEventListener(MapMouseEvent.CLICK,onCellClick,false,0,true);	
	 		pol.addEventListener(MapMouseEvent.DOUBLE_CLICK,onDoubleClick,false,0,true);	
	    }  
	      
	    private function clearPolygonsFromZoomLevel(zoom:Number):void {
			if(!previousZoomLevelCleared && !isNaN(previousZoomLevel)) {
				previousZoomLevelCleared=true;
				for (var z:Object in loadedTiles) {
					if (z as Number != currentZoomLevel) {
			 			for each (var zl2:Array in loadedTiles[z]) {
							for each(var pol2:Polygon in zl2) {
								pol2.removeEventListener(MapMouseEvent.ROLL_OVER,onRollOver);
								pol2.removeEventListener(MapMouseEvent.ROLL_OUT,onRollOut);
								pol2.removeEventListener(MapMouseEvent.CLICK,onCellClick);
								pol2.removeEventListener(MapMouseEvent.DOUBLE_CLICK,onDoubleClick);
								TweenLite.to(pol2.foreground,2,{alpha:0, onComplete:onFinishTween,onCompleteParams:[pol2]});
							}	
						}  	    	
					}
				}
			}   	    	
	    }  
	      
	      
		private	function onFinishTween(pol:Polygon):void {
			    gridPane.removeOverlay(pol);
		}  	    
	    
	    
	    
	    private function getPolygonCoordsFromTileCoords(x:Number,y:Number,zoom:Number):Array {
	    	  zoom = 17 - zoom;
		      var lon:Number      = -180; // x
		      var lonWidth:Number = 360; // width 360
		      var lat:Number       = -1;
		      var latHeight:Number = 2;
		
		      var tilesAtThisZoom:Number = 1 << (17 - zoom);
		      lonWidth  = 360.0 / tilesAtThisZoom;
		      lon       = -180 + (x * lonWidth);
		      latHeight = -2.0 / tilesAtThisZoom;
		      lat       = 1 + (y * latHeight);
		
		      // convert lat and latHeight to degrees in a transverse mercator projection
		      // note that in fact the coordinates go from about -85 to +85 not -90 to 90!
		      latHeight += lat;
		      latHeight = (2 * Math.atan(Math.exp(Math.PI * latHeight))) - (Math.PI / 2);
		      latHeight *= (180 / Math.PI);
		
		      lat = (2 * Math.atan(Math.exp(Math.PI * lat))) - (Math.PI / 2);
		      lat *= (180 / Math.PI);
		
		      latHeight -= lat;
		
		      if (lonWidth < 0) {
		         lon      = lon + lonWidth;
		         lonWidth = -lonWidth;
		      }
		
		      if (latHeight < 0) {
		         lat       = lat + latHeight;
		         latHeight = -latHeight;
		      }

		      return [
		      	new LatLng(lat,lon),
		      	new LatLng(lat+latHeight,lon),
		      	new LatLng(lat+latHeight,lon+lonWidth),
		      	new LatLng(lat,lon+lonWidth),
		      	new LatLng(lat,lon)];
	    }
	    
	    private function getPolygonOptions(stats:Array,ranges:Array):PolygonOptions {
	    	
	    	//Stats: num_occ,num_specimens,num_observations,num_other_bor,num_plantae,num_animalia,num_other_kingdoms
	    	
	    	var numTrams:Number = ranges.length;
	    	var tram:Number=0;
	    	for each(var r:Object in ranges) {
	    		if(stats[0]>=r.low && stats[0]<r.up) {
	    			break;
	    		}
	    		tram++;
	    	}
	    	
	    	var colNumber:Number = tram/(numTrams-1); 	    	
	    	if(colNumber>1 || isNaN(colNumber))
	    		colNumber=1;
	    		
  	    	var palette:Array = paletteYellowRed10;
  	    	var l:Number =Math.floor(colNumber*10) -1;
  	    	if(l<0)
  	    		l=0;
  	    	var col:Number = palette[l];								
	
			//Stats: num_occ,num_specimens,num_observations,num_other_bor,num_plantae,num_animalia,num_other_kingdoms
			var toolTip:String = "";
 			toolTip+="<p align=\"center\"><b>Total: "+numFormatter.format(stats[0]) +" occurrences</b></p>\n\n";

			var spePer:Number =  Math.round((stats[1]/stats[0])*100);
			var obsPer:Number =  Math.round((stats[2]/stats[0])*100);
			var otherBorPer:Number =  100-spePer-obsPer;
						
			var plantPer:Number =  Math.round((stats[4]/stats[0])*100);
			var AnimPer:Number =  Math.round((stats[5]/stats[0])*100);
			var OtherKingPer:Number =  100-plantPer-AnimPer;
			
			toolTip+="<b>Specimens:</b> "+spePer+"% ("+stats[1]+")          <b>Plants:</b> "+plantPer+"% ("+stats[4]+")\n";
			toolTip+="<b>Observations:</b> "+obsPer+"% ("+stats[2]+")    <b>Animals:</b> "+AnimPer+"% ("+stats[5]+")\n";
			toolTip+="<b>Other types:</b> "+otherBorPer+"% ("+stats[3]+")   <b>Other kingdoms:</b> "+OtherKingPer+"% ("+stats[6]+")";

			
/* 			var chart1:String = "http://chart.apis.google.com/chart?chs=150x60&cht=bhs&chco=7EA724,A9C248,D5D18A" + 
					"&chd=t:"+plantPer+"|"+AnimPer+"|"+OtherKingPer+"&chxt=x,t&chxl=0:|Plants|Animals|Other|1:|"+plantPer+"%|"+AnimPer+"%|"+OtherKingPer+"%|&chxs=0,000000,9|1,000000,10,-1&chf=bg,s,FFFF9E"

			var chart2:String = "http://chart.apis.google.com/chart?chs=150x60&cht=bhs&chco=A9252A,C44950,CA6E6F" + 
					"&chd=t:"+spePer+"|"+obsPer+"|"+otherBorPer+"&chxt=x,t&chxl=0:|Spec.|Observ.|Other|1:|"+spePer+"%|"+obsPer+"%|"+otherBorPer+"%|&chxs=0,000000,9|1,000000,10,-1&chf=bg,s,FFFF9E"

			toolTip+="\n<img id=\""+Math.random()*10000+"\" src=\""+chart1+"\" />";
			toolTip+="<img id=\""+Math.random()*10000+"\" src=\""+chart2+"\" />"; */
			
			
			return new PolygonOptions({
					strokeStyle: new StrokeStyle({
			        	color: 0x818181,
			        	thickness: 0,
			        	alpha: 1}), 
			        fillStyle: new FillStyle({
			        	color: col,
			        	alpha:1}),
			        tooltip: toolTip
			        
			});	    	
	    }
	    
	    public function clearOverlay():void {
	    	
	    	//gridPane.clear();
	    	gridPane.visible=false;   	
	    }
    
	}
}